﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;

namespace Sttplay.Net.UDP
{
    public class RUDP
    {

        private Socket udpSocket;
        public Socket Socket { get { return udpSocket; } }
        private List<Socket> valid = new List<Socket>();
        private byte[] buffer = new byte[RCTP.BUFF_SIZE + RCTP.HEAD_SIZE];

        public event Action<byte[], bool, EndPoint> onRecv;
        public event Action<RCTP.Message, RCTP.SendState> onSend;
        public event Action<string> onErrorLog;

        private RCTP rctp;
        private List<EndPoint> clearList = new List<EndPoint>();
        protected IPAddress joinAddress;
        private long sendBytes;
        private long recvBytes;
        public RUDP()
        {
            rctp = new RCTP();
            rctp.SendImp = SendTo;
            rctp.onRecv += OnRecvEvent;
            rctp.onSend += OnSendEvent;
            RCTP.onErrorLog += OnErrorLogEvent;
        }

        private void OnSendEvent(RCTP.Message msg, RCTP.SendState state)
        {
            try
            {
                if (onSend != null)
                    onSend(msg, state);
            }
            catch (Exception ex)
            {
                OnErrorLogEvent(ex.ToString());
            }

        }

        public uint GetTimestamp()
        {
            return RCTP.GetTimestamp();
        }

        /// <summary>
        /// 设置选项
        /// </summary>
        /// <param name="option"></param>
        /// <param name="value"></param>
        public void SetOption(RCTP.Option option, uint value)
        {
            rctp.SetOption(option, value);
        }

        public void SetOption(RCTP.Option option, float value)
        {
            rctp.SetOption(option, value);
        }


        /// <summary>
        /// 初始化 Small RUDP
        /// </summary>
        /// <param name="port">本地端口号</param>
        /// <returns></returns>
        public virtual bool Initialize(IPAddress ip, int port)
        {
            Terminate();
            uint IOC_IN = 0x80000000;
            uint IOC_VENDOR = 0x18000000;
            uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
            udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            try
            {
                try
                {
                    udpSocket.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null);
                }
                catch { }

                udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
                udpSocket.Bind(new IPEndPoint(ip, port));
            }
            catch (Exception ex)
            {
                OnErrorLogEvent(ex.ToString());
                return false;
            }
            udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1);
            rctp.Initialize();
            return true;
        }

        /// <summary>
        /// 加入多播组
        /// </summary>
        /// <param name="ip"></param>
        /// <returns></returns>
        public bool JoinMulticastGroup(IPAddress multicastAddr)
        {
            if (udpSocket == null) return false;
            if (joinAddress != null) return false;
            joinAddress = multicastAddr;
            udpSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(joinAddress));
            return true;
        }

        /// <summary>
        /// 从多播组中移除
        /// </summary>
        public void DropMulticastGroup()
        {
            if (udpSocket == null) return;
            if (joinAddress == null) return;
            udpSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DropMembership, new MulticastOption(joinAddress));
            joinAddress = null;
        }

        /// <summary>
        /// 驱动 RUDP
        /// </summary>
        public virtual void Update(int timeout = 0)
        {
            HandleRecv();
            rctp.Update(timeout);
        }

        /// <summary>
        /// 设置下次发送的帧的ProtocolFrame
        /// </summary>
        /// <param name="frame"></param>
        public void SetProtocolFrame(ulong frame)
        {
            rctp.SetProtocolFrame(frame);
        }

        /// <summary>
        /// 获取当前接收帧的ProtocolFrame
        /// </summary>
        /// <returns></returns>
        public ulong GetProtocolFrame()
        {
            return rctp.GetProtocolFrame();
        }

        /// <summary>
        /// 清除缓冲包
        /// </summary>
        public void ClearCache()
        {
            rctp.Clear();
        }

        /// <summary>
        /// 接收消息
        /// </summary>
        private void HandleRecv()
        {
            if (udpSocket == null)
                return;
            valid.Clear();
            valid.Add(udpSocket);

            while (true)
            {
                Socket.Select(valid, null, null, 0);
                if (valid.Count <= 0)
                    break;

                EndPoint remote = new IPEndPoint(IPAddress.Any, 0);

                try
                {
                    int len = udpSocket.ReceiveFrom(buffer, ref remote);
                    if (len <= 0)
                        continue;
                    rctp.InputOriginRecv(buffer, 0, len, remote);
                    recvBytes += len;
                }
                catch (Exception ex)
                {
                    OnErrorLogEvent(ex.ToString());
                }
            }
        }


        protected virtual void OnRecvEvent(byte[] buffer, bool reliable, object param)
        {
            try
            {
                if (onRecv != null)
                    onRecv(buffer, reliable, (EndPoint)param);
            }
            catch (Exception ex)
            {
                OnErrorLogEvent(ex.ToString());
            }

        }
        protected void OnErrorLogEvent(string error)
        {
            try
            {
                if (onErrorLog != null)
                    onErrorLog(error);
            }
            catch
            { }

        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">buffer</param>
        /// <param name="remote">end point</param>
        /// <param name="reliable">reliable</param>
        public virtual void SendTo(byte[] buffer, int index, int len, EndPoint remote, bool reliable)
        {
            rctp.SendTo(buffer, index, len, reliable && joinAddress == null, remote);
        }

        private void SendTo(byte[] buffer, int index, int len, object param)
        {
            try
            {
                udpSocket.SendTo(buffer, index, len, SocketFlags.None, (EndPoint)param);
                sendBytes += len;
            }
            catch { }
        }

        /// <summary>
        /// 终止 RUDP
        /// </summary>
        public virtual void Terminate()
        {
            DropMulticastGroup();
            if (udpSocket != null)
                udpSocket.Close();
            udpSocket = null;
            rctp.Terminate();
            sendBytes = recvBytes = 0;
        }
        ~RUDP()
        {
            rctp.SendImp = null;
            rctp.onRecv -= OnRecvEvent;
            rctp.onSend -= OnSendEvent;
            RCTP.onErrorLog -= OnErrorLogEvent;
        }
    }

}